#include "PreCompile.hpp"
#include "Project.hpp"
#include "Module.hpp"
#include "ErrorListener.hpp"
#include "LibBuildNGInit.hpp"

namespace BuildNG
{
  static Glib::ustring get_abs_path(const Glib::ustring &path)
  {
    if(Glib::path_is_absolute(path))
      return path;
    else
      return Glib::Path::canonicalize_path(Glib::build_filename(Glib::get_current_dir(),path));
  }

  Project::Project(const Glib::ustring &src_file_name)
  {
    assert(is_libbuildng_initialized() && "Please call init_libbuildng before using any functionality from this module.");

    root_dir = get_abs_path(Glib::path_get_dirname(src_file_name));

    Xml::Document doc(src_file_name);

    Xml::Element *root = doc.get_root_node();
    if(!XmlUtils::compare_element_name(*root,"project"))
    {
      throw Xml::ContentException(*root,"The root element should be \"project\".");
    }
    
    set_name(root->get_attribute_value("name"));

    Xml::ElementList children = root->get_child_elements();
    for(int i = 0;i < (int)children.size();i++)
    {
      Xml::Element *cur_element = children[i];
      assert(cur_element);
      
      const ModuleTypeInfo *module_type_info = get_module_type_info(cur_element->get_name());
      if(!module_type_info)
        throw Xml::ContentException(*cur_element,"Unknown module type: \"" + cur_element->get_name() + "\".");

      Glib::RefPtr<Module> cur_module = module_type_info->create_func();
      cur_module->set_module_dir(get_abs_path(Glib::path_get_dirname(src_file_name)));
      cur_module->load_from_xml(*cur_element);
      
      modules.add(cur_module);
    }
  }

  Project::~Project()
  {
  }

  Glib::RefPtr<Project> Project::load_from_file(const Glib::ustring &src_file_name)
  {
    return Glib::RefPtr<Project>(new Project(src_file_name));
  }
	
  bool Project::build(const Glib::ustring &output_root_dir,
    Glib::RefPtr<ErrorListener> error_listener)
  {
    BuildContext context(root_dir,Glib::build_filename(root_dir,".build"));
    context.begin();

    bool success = true;
    NamedObjectCollection<Module>::Iterator it = modules.get_iterator();
    while(it.move_next())
    {
      context.push_module(it.cur());
      error_listener->begin_build_module(it.cur());

      bool cur_success = it.cur()->build(context,error_listener);
      if(!cur_success)
        success = false;

      error_listener->end_build_module(it.cur());
      context.pop_module();
    }

    context.end();

    return success;
  }

  int Project::get_num_modules() const
  {
    return modules.get_size();
  }

  Glib::RefPtr<Module> Project::get_module(const Glib::ustring &name)
  {
    return modules.try_get(name);
  }

  std::vector<Glib::RefPtr<Module> > Project::get_modules_list()
  {
    std::vector<Glib::RefPtr<Module> > ret;
    NamedObjectCollection<Module>::Iterator it = modules.get_iterator();
    while(it.move_next())
    {
      ret.push_back(it.cur());
    }
    return ret;
  }

  /////////////
  // BuildContext
  BuildContext::BuildContext(const Glib::ustring &project_root_dir,
    const Glib::ustring &tmp_root_dir)
  {
    assert(Glib::path_is_absolute(project_root_dir));
    assert(Glib::path_is_absolute(tmp_root_dir));

    this->project_root_dir = project_root_dir;
    this->tmp_root_dir = tmp_root_dir;
  }

  void BuildContext::begin()
  {
    // make sure the temp dir exists.
    if(!Glib::file_test(tmp_root_dir,Glib::FILE_TEST_EXISTS))
    {
      int res = g_mkdir_with_parents(tmp_root_dir.c_str(),0777);
      if(res == -1)
      {
        throw BuildException("Couldn't create the temp directory: \"" +
          tmp_root_dir + "\"");
      }
    }
    else if(!Glib::file_test(tmp_root_dir,Glib::FILE_TEST_IS_DIR))
    {
      throw BuildException("\"" + tmp_root_dir + "\" is not a directory.");
    }
  }

  void BuildContext::end()
  {
    assert(module_stack.empty());
  }

  void BuildContext::push_module(Glib::RefPtr<Module> module)
  {
    ModuleInfo module_info;
    module_info.module = module;
    module_info.cur_dir = module->get_module_dir();
    module_info.tmp_dir = get_temp_dir_for_module(module);

    // make sure the tmp dir exists.
    int res = g_mkdir_with_parents(module_info.tmp_dir.c_str(),0777);
    if(res == -1)
    {
      throw BuildException("Couldn't create the temp directory: \"" +
        tmp_root_dir + "\"");
    }

    module_stack.push(module_info);
  }

  void BuildContext::pop_module()
  {
    assert(!module_stack.empty());
    module_stack.pop();
  }

  void BuildContext::copy_to_output_dir(const Glib::ustring &src_file_name,
    const Glib::ustring &relative_dest_file_name,OutputDirType out_dir_type)
  {
    copy_file(src_file_name,"c:/test/" + relative_dest_file_name);
  }

  const Glib::ustring& BuildContext::get_cur_dir() const
  {
    assert(!module_stack.empty());
    return module_stack.top().cur_dir;
  }

  const Glib::ustring& BuildContext::get_cur_tmp_dir() const
  {
    assert(!module_stack.empty());
    return module_stack.top().tmp_dir;
  }

  Glib::ustring BuildContext::get_temp_dir_for_module(Glib::RefPtr<Module> module) const
  {
    Glib::ustring cur_rel_path = Glib::Path::get_relative_path(
      module->get_module_dir(),project_root_dir);
        
    std::vector<Glib::ustring> elems = Glib::Path::split_into_elements(cur_rel_path);
    Glib::ustring ret = tmp_root_dir;
    for(int i = 0;i < (int)elems.size();i++)
    { 
      if(elems[i] == "..")
        ret = Glib::build_filename(ret,"_parent");
      else if(elems[i][0] == '_')
        ret = Glib::build_filename(ret,"_" + elems[i]);
      else
        ret = Glib::build_filename(ret,elems[i]);
    }
    return ret;
  }
}
